﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;

namespace Anagramme.Server.Service
{
    /// <summary>
    /// Manager des callbacks du serveurs
    /// </summary>
    public class CallbacksManager
    {
        #region Static Fields
        private static readonly CallbacksManager _default = new CallbacksManager();
        #endregion

        #region Static Properties
        /// <summary>
        /// Instance par défaut du manager
        /// </summary>
        public static CallbacksManager Default
        {
            get { return _default; }
        }
        #endregion

        #region Constructors
        private CallbacksManager()
        {
            this._registry = new Dictionary<Guid, List<object>>();
        }
        #endregion

        #region Fields
        private Dictionary<Guid, List<object>> _registry;
        #endregion

        #region Methods
        private void CleanUp()
        {
            List<Guid> keysToDelete = new List<Guid>();

            foreach (KeyValuePair<Guid, List<object>> item in this._registry)
            {
                item.Value.RemoveAll(c =>
                {
                    ICommunicationObject comObject = c as ICommunicationObject;
                    return object.ReferenceEquals(comObject, null) 
                        || (comObject.State != CommunicationState.Opened 
                        && comObject.State != CommunicationState.Opening);
                });

                if (item.Value.Count.Equals(0))
                {
                    keysToDelete.Add(item.Key);
                }
            }

            foreach (Guid keyToDelete in keysToDelete)
            {
                this._registry.Remove(keyToDelete);
            }
        }

        /// <summary>
        /// Récupère un callback en fonction de son type et du <see cref="System.Guid"/> associé
        /// </summary>
        /// <typeparam name="T">Type du callback</typeparam>
        /// <param name="token"><see cref="System.Guid"/> associé</param>
        /// <returns>Retourne le callback correspondant</returns>
        public T GetCallback<T>(Guid token) where T : class
        {
            this.CleanUp();
            if (this._registry.ContainsKey(token))
            {
                return this._registry[token].OfType<T>().SingleOrDefault();
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Récupère la liste des callbacks du type passé en paramètre
        /// </summary>
        /// <typeparam name="T">Type de callbacks à récupérer</typeparam>
        /// <returns>Retourne la liste des callbacks correspondont</returns>
        public IEnumerable<T> GetCallbacks<T>() where T : class
        {
            this.CleanUp();
            List<T> returnValue = new List<T>();
            IList<Guid> keys = this._registry.Keys.ToList();
            foreach (Guid key in keys)
            {
                if (this._registry.ContainsKey(key))
                {
                    T callback = this._registry[key].OfType<T>().SingleOrDefault();
                    if (!object.ReferenceEquals(callback, null))
                    {
                        returnValue.Add(callback);
                    }
                }
            }
            return returnValue;
        }

        /// <summary>
        /// Récupère la liste des callbacks associés à un <see cref="System.Guid"/>
        /// </summary>
        /// <returns><see cref="System.Guid"/> associé</returns>
        /// <returns>Retourne la liste des callbacks correspondont</returns>
        public IEnumerable<object> GetCallbacks(Guid token)
        {
            this.CleanUp();
            if (this._registry.ContainsKey(token))
            {
                return this._registry[token];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Récupère la liste des callbacks du type passé en paramètre, hormi celui associé au <see cref="System.Guid"/> passé en paramètre
        /// </summary>
        /// <typeparam name="T">Type de callbacks à récupérer</typeparam>
        /// <param name="token"><see cref="System.Guid"/> à exclure</param>
        /// <returns>Retourne la liste des callbacks correspondont</returns>
        public IEnumerable<T> GetOthersCallbacks<T>(Guid token) where T : class
        {
            this.CleanUp();
            List<T> returnValue = new List<T>();
            IList<Guid> keys = this._registry.Keys.ToList();
            keys.Remove(token);
            foreach (Guid key in keys)
            {
                if (this._registry.ContainsKey(key))
                {
                    T callback = this._registry[key].OfType<T>().SingleOrDefault();
                    if (!object.ReferenceEquals(callback, null))
                    {
                        returnValue.Add(callback);
                    }
                }
            }
            return returnValue;
        }

        /// <summary>
        /// Récupère le <see cref="System.Guid"/> associé au callback passé en paramètre
        /// </summary>
        /// <typeparam name="T">Type du callback</typeparam>
        /// <param name="callback">Callback associé</param>
        /// <returns>Retourne le <see cref="System.Guid"/> correspondant</returns>
        public Guid GetToken<T>(T callback)
        {
            this.CleanUp();
            foreach (KeyValuePair<Guid, List<object>> values in this._registry)
            {
                if (values.Value.Contains(callback))
                {
                    return values.Key;
                }
            }
            return Guid.Empty;
        }

        /// <summary>
        /// Enregistre un callback dans le manager
        /// </summary>
        /// <typeparam name="T">Type du callback</typeparam>
        /// <param name="token">Clé associée au callback</param>
        /// <param name="callback">Callback à enregistrer</param>
        public void Register<T>(Guid token, T callback) where T : class
        {
            this.CleanUp();
            if (!this._registry.ContainsKey(token))
            {
                this._registry.Add(token, new List<object>());
            }

            if (!this._registry[token].Contains(callback))
            {
                this._registry[token].Add(callback);
            }
        }

        /// <summary>
        /// Supprimer tout les callbacks associés à un <see cref="System.Guid"/> du manager
        /// </summary>
        /// <param name="token"><see cref="System.Guid"/> associé aux callbacks à supprimer</param>
        public void Unregister(Guid token)
        {
            this.CleanUp();
            if (this._registry.ContainsKey(token))
            {
                this._registry[token].Clear();
                this._registry.Remove(token);
            }
        }

        /// <summary>
        /// Supprimer un callback du manager
        /// </summary>
        /// <typeparam name="T">Type du callback</typeparam>
        /// <param name="callback">Callback à supprimer</param>
        public void Unregister<T>(T callback) where T : class
        {
            this.CleanUp();
            foreach (KeyValuePair<Guid, List<object>> item in this._registry)
            {
                if (item.Value.Contains(callback))
                {
                    item.Value.Remove(callback);
                }
            }
        }
        #endregion
    }
}
